package proxy

import (
	"fmt"
	"go-proxy/common"
	"go-proxy/config"
	"go-proxy/proxy/register"
	"sort"
	"sync"
)

var (
	serviceMap     = make(map[string]*config.Service)
	serviceMapLock sync.RWMutex

	roundRobinMap  = make(map[string]string)
	roundRobinLock sync.RWMutex
)

func InitService() {
	services := config.ProxyConfigData.Services
	for _, serviceConfig := range services {
		if serviceConfig.Enable == false {
			continue
		}
		if serviceConfig.Type != common.StaticService && serviceConfig.Type != common.DnsService {
			err := register.InitRegisterCenter(&serviceConfig)
			if err != nil {
				panic(err)
			}
		}
		setService(&serviceConfig)
	}
}

func setService(service *config.Service) {
	serviceMapLock.Lock()
	serviceMap[service.Name] = service
	serviceMapLock.Unlock()
}

func getService(name string) *config.Service {
	serviceMapLock.RLock()
	defer serviceMapLock.RUnlock()
	return serviceMap[name]
}

func setRoundRobin(name string, path string) {
	roundRobinLock.Lock()
	defer roundRobinLock.Unlock()
	roundRobinMap[name] = path
}

func getRoundRobin(name string) string {
	roundRobinLock.RLock()
	defer roundRobinLock.RUnlock()
	return roundRobinMap[name]
}

func getLoadBalance(route *config.Route) (string, error) {
	service := getService(route.TargetService)
	if service == nil {
		return "", fmt.Errorf("service not exist")
	}

	if service.Type == common.ConsulService || service.Type == common.NacosService {
		return handleRegisterService(service, route)
	}

	return handleStaticService(service)
}

func handleStaticService(service *config.Service) (string, error) {
	if service.TargetPath == nil || len(service.TargetPath) <= 0 {
		return "", fmt.Errorf("register no targetPath")
	}

	if len(service.TargetPath) == 1 {
		return service.TargetPath[0].Path, nil
	}

	path := getRoundRobin(service.Name)
	targetPaths := filterPath(service.TargetPath, path)

	if len(targetPaths) == 1 {
		setRoundRobin(service.Name, targetPaths[0].Path)
		return targetPaths[0].Path, nil
	}

	sortByWeight(targetPaths)
	setRoundRobin(service.Name, targetPaths[0].Path)
	return targetPaths[0].Path, nil
}

func handleRegisterService(service *config.Service, route *config.Route) (string, error) {
	instances, err := register.GetServiceInstances(service, route)
	if err != nil {
		return "", err
	}

	if len(instances) <= 0 {
		return "", fmt.Errorf("register no instance")
	}

	if len(instances) == 1 {
		return instances[0].Address, nil
	}

	path := getRoundRobin(service.Name)
	targetInstances := filterInstancePath(instances, path)

	setRoundRobin(service.Name, targetInstances[0].Address)
	return targetInstances[0].Address, nil
}

func sortByWeight(items []config.TargetPath) {
	sort.Slice(items, func(i, j int) bool {
		return items[i].Weight > items[j].Weight
	})
}

func filterPath(items []config.TargetPath, path string) []config.TargetPath {
	if len(path) <= 0 {
		return items
	}

	list := make([]config.TargetPath, 0)
	for _, item := range items {
		if item.Path != path {
			list = append(list, item)
		}
	}
	return list
}

func filterInstancePath(instances []*register.InstanceInfo, path string) []*register.InstanceInfo {
	if len(path) <= 0 {
		return instances
	}

	list := make([]*register.InstanceInfo, 0)
	for _, item := range instances {
		if item.Address != path {
			list = append(list, item)
		}
	}
	return list
}
